SEO: server-render list pages, GET-based search, URL pagination (size=10), and SSR locale fallback#9
Open
Copilot wants to merge 7 commits into
Open
SEO: server-render list pages, GET-based search, URL pagination (size=10), and SSR locale fallback#9Copilot wants to merge 7 commits into
Copilot wants to merge 7 commits into
Conversation
Copilot
AI
changed the title
[WIP] Implement SEO improvements for GoodActionHub
SEO: server-render list pages, GET-based search, and URL pagination (size=10)
Jun 6, 2026
TechQuery
requested changes
Jun 7, 2026
| const { t } = useTranslation(); | ||
| type PageSearchParams = Promise<{ | ||
| page?: string; | ||
| query?: string; |
Member
There was a problem hiding this comment.
Suggested change
| query?: string; | |
| keywords?: string; |
Comment on lines
+51
to
+53
| if (page > 1) params.set('page', String(page)); | ||
| const queryString = params.toString(); | ||
| return queryString ? `/restaurants?${queryString}` : '/restaurants'; |
Member
There was a problem hiding this comment.
Suggested change
| if (page > 1) params.set('page', String(page)); | |
| const queryString = params.toString(); | |
| return queryString ? `/restaurants?${queryString}` : '/restaurants'; | |
| if (page > 1) params.set('page', String(page)); | |
| return (params + '') ? `/restaurants?${params}` : '/restaurants'; |
Comment on lines
+190
to
+192
| pagedRestaurants.map((restaurant) => { | ||
| const types = getAccessibilityTypes(restaurant); | ||
| const isNavigating = navigationLoading === restaurant.name; | ||
| const markerUrl = `https://uri.amap.com/marker?address=${encodeURIComponent(restaurant.address)}&name=${encodeURIComponent(restaurant.name)}`; |
Member
There was a problem hiding this comment.
Suggested change
| pagedRestaurants.map((restaurant) => { | |
| const types = getAccessibilityTypes(restaurant); | |
| const isNavigating = navigationLoading === restaurant.name; | |
| const markerUrl = `https://uri.amap.com/marker?address=${encodeURIComponent(restaurant.address)}&name=${encodeURIComponent(restaurant.name)}`; | |
| pagedRestaurants.map((restaurant) => { | |
| const types = getAccessibilityTypes(restaurant); | |
| const { address, name } = restaurant; | |
| const markerUrl = `https://uri.amap.com/marker?${new URLSearchParams(restaurant{ address, name })}`; |
|
|
||
| const courses = await fetchTutoringCatalog(); | ||
|
|
||
| const allTags = Array.from(new Set(courses.flatMap((course) => course.tags))); |
Member
There was a problem hiding this comment.
Suggested change
| const allTags = Array.from(new Set(courses.flatMap((course) => course.tags))); | |
| const allTags = [...new Set(courses.flatMap(({ tags }) => tags))]; |
Copilot
AI
changed the title
SEO: server-render list pages, GET-based search, and URL pagination (size=10)
SEO: server-render list pages, GET-based search, URL pagination (size=10), and SSR locale fallback
Jun 7, 2026
[add] Traditional Chinese translation data [optimize] update Upstream packages
There was a problem hiding this comment.
Pull request overview
This PR aims to improve SEO and shareability of the three “list” surfaces by moving search/filter/pagination state into URL-driven server-rendered pages, and by adding SSR language selection/fallback for list-page UI text.
Changes:
- Reworked
/activities,/restaurants, and/tutoringlist pages to be server components with GET-based search and URL pagination (page size = 10). - Introduced SSR language loading via
mobx-i18n+ new translation maps forzh-CN,zh-TW, anden-US. - Aligned activities data fetching by adding
fetchActivitiesCatalog()and reusing it in the API route and activities pages.
Reviewed changes
Copilot reviewed 20 out of 22 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| public/locales/zh-TW/translation.json | Adds Traditional Chinese i18next translation namespace data. |
| public/locales/zh-TW/common.json | Adds Traditional Chinese i18next common namespace data. |
| package.json | Updates scripts/deps; introduces shadcn-helper install hook and dependency upgrades. |
| lib/pagination.ts | Adds shared page parsing + visible page calculation helpers. |
| lib/activities.ts | Adds fetchActivitiesCatalog() to centralize activities fetch behavior. |
| i18n/zh-TW.ts | Adds SSR translation map for Traditional Chinese. |
| i18n/zh-CN.ts | Adds SSR translation map for Simplified Chinese. |
| i18n/en-US.ts | Adds SSR translation map for English (US). |
| i18n/index.ts | Adds SSR i18n store + cookie/header/query language loading. |
| i18n/config.ts | Extends client i18next config to support zh-TW and formatting tweaks. |
| eslint.config.ts | Adds React version setting and keeps existing TS-eslint rule override. |
| components/index.ini | Declares a shadcn registry component to be installed (pager). |
| components.json | Adds a custom shadcn registry URL. |
| app/tutoring/page.tsx | Converts tutoring list to SSR with GET search + URL tag selection + pagination. |
| app/tutoring/[slug]/page.tsx | Adds SSR i18n text usage in tutoring detail page. |
| app/restaurants/page.tsx | Converts restaurants list to SSR with GET search + URL filter + pagination. |
| app/restaurants/[id]/page.tsx | Adds SSR i18n text usage in restaurant detail page. |
| app/api/data/route.ts | Uses fetchActivitiesCatalog() for consistent upstream fetch behavior. |
| app/activities/page.tsx | Converts activities list to SSR with GET search + pagination and SSR locale loading. |
| app/activities/[id]/page.tsx | Uses fetchActivitiesCatalog() + SSR i18n text usage in activity detail page. |
| .gitignore | Ignores components/ui/ (shadcn UI components). |
Comments suppressed due to low confidence (1)
app/api/data/route.ts:14
fetchActivitiesCatalog()now throws on upstream fetch errors, but the API route catches everything and returns HTTP 500. Previously, upstream fetch failures returned 502 (bad gateway), which more accurately signals an upstream dependency issue. Consider preserving 502 for fetch failures so monitoring/clients can distinguish upstream vs server errors.
export async function GET() {
try {
const externalData = await fetchActivitiesCatalog();
const data = externalData.map(transformItem);
return NextResponse.json(data);
} catch (err) {
console.error('Failed to fetch data from external API:', err);
return NextResponse.json({ error: 'Failed to load data' }, { status: 500 });
}
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1
to
+4
| export function parsePage(rawPage?: string): number { | ||
| const parsed = Number.parseInt(rawPage ?? '1', 10); | ||
| return Number.isFinite(parsed) && parsed > 0 ? parsed : 1; | ||
| } |
Comment on lines
+11
to
+13
| import { createI18nStore, loadSSRLanguage } from '@/i18n'; | ||
| import { Pager } from '@/components/ui/mobx-restful-shadcn/pager'; | ||
| import { parsePage } from '@/lib/pagination'; |
Comment on lines
+5
to
+9
| import { createI18nStore, loadSSRLanguage } from '@/i18n'; | ||
| import FoodAIDialog from '@/components/FoodAIDialog'; | ||
| import SafeTranslation from '@/components/SafeTranslation'; | ||
| import { Pager } from '@/components/ui/mobx-restful-shadcn/pager'; | ||
| import { fetchBitesCatalog, BitesRestaurant } from '@/lib/bitesCatalog'; | ||
| import { parsePage } from '@/lib/pagination'; |
Comment on lines
+6
to
+10
| import { createI18nStore, loadSSRLanguage } from '@/i18n'; | ||
| import { EventCard } from '@/components/EventCard'; | ||
| import { FilterBar } from '@/components/FilterBar'; | ||
| import { GitCodeIcon } from '@/components/icons/GitCodeIcon'; | ||
| import { GitHubIcon } from '@/components/icons/GitHubIcon'; | ||
|
|
||
| import { DeadlineItem, EventData } from '@/lib/data'; | ||
| import { useEventStore } from '@/lib/store'; | ||
| import Fuse from 'fuse.js'; | ||
|
|
||
| import { DateTime } from 'luxon'; | ||
| import Link from 'next/link'; | ||
| import { useEffect, useMemo } from 'react'; | ||
| import { useTranslation } from 'react-i18next'; | ||
| import { Pager } from '@/components/ui/mobx-restful-shadcn/pager'; |
Comment on lines
161
to
+165
| <div className="bg-white/80 backdrop-blur-sm rounded-xl p-6 shadow-lg border border-white/20 mb-8"> | ||
| <FilterBar /> | ||
| <form action="/activities" method="get" className="flex gap-3"> | ||
| <input | ||
| type="text" | ||
| name="query" |
Comment on lines
13
to
15
| "start": "next start", | ||
| "test": "next typegen && lint-staged && tsc --noEmit", | ||
| "test": "next typegen && lint-staged && git add . && tsc --noEmit", | ||
| "knip": "knip", |
Comment on lines
+6
to
+8
| "registries": { | ||
| "@mobx-restful-shadcn": "https://mobx-restful-shadcn.idea2.app/r/{name}.json" | ||
| }, |
Comment on lines
+11
to
+15
| const i18nData = { | ||
| 'zh-CN': zhCN, | ||
| 'zh-TW': () => import('./zh-TW'), | ||
| 'en-US': () => import('./en-US'), | ||
| }; |
Comment on lines
+42
to
+46
| export const LanguageName: Record<LanguageCode, string> = { | ||
| 'zh-CN': '简体中文', | ||
| 'zh-TW': '繁體中文', | ||
| 'en-US': 'English', | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR addresses SEO for the three list surfaces by moving rendering/filtering state into URL-driven server pages. It also standardizes pagination to URL parameters with a 10-item page size and visible page links at the end of each list.
Scope (issue requirements)
GETform submissions.pagequery-param pagination in units of 10 with centered page links.Server-rendered list pages
app/activities/page.tsxapp/restaurants/page.tsxapp/tutoring/page.tsxsearchParamson the server, compute filtered datasets, and slice results by page.app/activities/page.tsxnow also parsesAccept-Languageon the server to choose zh-CN/en fallback copy for key list-page UI text.URL-driven search + filtering
GETforms:/activities?query=.../restaurants?query=...&filter=.../tutoring?query=...&tag=...Pagination behavior
lib/pagination.ts(parsePage,getVisiblePages).components/Pager.tsxand applied it to all three list pages.PAGE_SIZE = 10pagequery paramData access alignment
fetchActivitiesCatalog()inlib/activities.tsand reused it in activities list/detail and/api/datapath to keep fetch behavior consistent.Human changes